import schema, { ValidateOption } from 'async-validator';
import clone from './clone';

interface IObject {
  [propName: string]: any;
}

export type ValidateRule<T extends IObject> = {
  [propName in keyof T]?: ValidateRuleItem<T> | Array<ValidateRuleItem<T>>;
};

export type ValidateRuleType =
  | 'string'
  | 'number'
  | 'boolean'
  | 'method'
  | 'regexp'
  | 'integer'
  | 'float'
  | 'array'
  | 'object'
  | 'enum'
  | 'date'
  | 'url'
  | 'hex'
  | 'email'
  | 'any';

export interface ValidateRuleItem<T extends IObject> {
  /**
   * 内置校验类型
   */
  type?: ValidateRuleType | 'price' | 'date-string' | 'datetime-string';
  allowEmpty?: boolean;
  enum?: string;
  /** 字段长度 */
  len?: number;
  /** 最小长度 */
  min?: number;
  /** 最小长度 */
  max?: number;
  /** 正则表达式 */
  pattern?: RegExp | string;
  /** 是否必选 */
  required?: boolean;
  message?: string;
  /** 是否启用（用于联动） */
  enable?: (value: T) => boolean;
  /** 校验前转换字段值 */
  transform?: (value: T) => any;
  asyncValidator?: (
    rule: ValidateRule<T>,
    /**
     * 字段的值
     */
    value: any,
    callback: (error?: string | string[] | void) => void,
    source: T,
    options: ValidateOption,
  ) => void | Promise<void>;
  validator?: (
    rule: ValidateRule<T>,
    /**
     * 字段的值
     */
    value: any,
    callback: (error?: string | string[] | void) => void,
    source: T,
    options: ValidateOption,
  ) => void;
}

interface ValidateErrorItem {
  message: string;
  field: string;
}
interface ValidateErrorFields {
  [propName: string]: ValidateErrorItem[] | undefined;
}
export interface ValidateError {
  /**
   * 第一条错误信息
   */
  message: string;
  errors: ValidateErrorItem[];
  fields: ValidateErrorFields;
}

export const syncValidate = <T extends IObject>(data: T, rule: ValidateRule<T>, allData?: T) => {
  return new Promise<void>((resolve, reject) => {
    validate<T>(
      data,
      rule,
      (e) => {
        if (e) return reject(e);
        resolve();
      },
      allData,
    );
  });
};

/**
 * 校验数据
 * @param data 需要检验的数据对象
 * @param rule 规则
 * @param cb 结果回调
 * @param allData 当对单个字段进行校验时 此参数传入完整的数据 用于实现 enable 联动根据条件启用和禁用某个规则
 */
export const validate = <T extends IObject>(
  data: T,
  _rule: ValidateRule<T>,
  cb: (e: ValidateError | null) => void,
  allData?: T,
) => {
  let rule = clone(_rule, undefined, undefined, undefined, undefined);
  for (const field in rule) {
    if (rule.hasOwnProperty(field)) {
      // 浅拷贝 防止enable数据残留
      const item = Array.isArray(rule[field])
        ? (rule[field] as Array<ValidateRuleItem<T>>).slice()
        : { ...rule[field] };
      if (typeof item === 'object') {
        /**
         * 对规则进行修饰 实现自定义类型 或 对默认类型校验器的替换
         */
        const check = (ruleItem: ValidateRuleItem<T>) => {
          /**
           * 实现 enable 根据某个值 启用和禁用条件
           */
          if (ruleItem.enable) {
            if (!ruleItem.enable(allData || data)) {
              ruleItem.type = 'any';
              if (ruleItem.required) delete ruleItem.required;
              if (ruleItem.validator) delete ruleItem.validator;
              if (ruleItem.asyncValidator) delete ruleItem.asyncValidator;
              return ruleItem;
            }
          }
          // type number 空值报错
          if (ruleItem.type === 'number') {
            delete ruleItem.type;
            ruleItem.validator = checkNumberValidator;
          }
          // 正则
          if (ruleItem.type === 'regexp') {
            delete ruleItem.type;
            ruleItem.validator = checkRegValidator;
          }
          // 金额
          if (ruleItem.type === 'price') {
            delete ruleItem.type;
            ruleItem.validator = checkPriceValidator;
          }
          // 对日期类型的重写
          if (ruleItem.type === 'date-string') {
            delete ruleItem.type;
            ruleItem.validator = checkDateValidator;
          }
          // 对日期类型的重写
          if (ruleItem.type === 'datetime-string') {
            delete ruleItem.type;
            ruleItem.validator = checkDateTimeValidator;
          }
          return ruleItem;
        };
        if (Array.isArray(item)) {
          item.forEach(check);
        } else {
          rule[field] = check(item);
        }
      }
    }
  }
  // console.log('[rule] ', rule);
  // think.logger.debug(JSON.stringify(rule));
  let validator = new schema(rule as any);
  // validator.messages();
  validator.validate(
    data,
    {
      messages: {
        required: '%s 必填',
      },
    } as any,
    (errors: any, fields: any) => {
      // console.log('[error fields] ', fields, errors);
      if (!!errors && !!fields) {
        return cb({
          message: errors[0].message || '参数错误',
          errors,
          fields,
        });
      }
      cb(null);
    },
  );
};

export const allow = (data: any, allowArray: string[]) => {
  let _data: any = {};
  for (let i = 0, l = allowArray.length; i < l; i++) {
    let key = allowArray[i];
    if (data.hasOwnProperty(key)) _data[key] = data[key];
  }
  return _data;
};

export const unallow = (data: object, unallowArray: string[]) => {
  let _data: any = Object.assign({}, data);
  for (let i = 0, l = unallowArray.length; i < l; i++) {
    let key = unallowArray[i];
    if (_data.hasOwnProperty(key)) delete _data[key];
  }
  return _data;
};

const checkPriceValidator = (_rule: any, value: any, callback: any) => {
  if (!_rule.required && value === undefined) return callback();
  if (_rule.allowEmpty && value === '') return callback();
  let a = /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/;
  if (!a.test(value)) return callback(new Error(_rule.message || '请输入正确的金额'));
  callback();
};
const checkDateValidator = (_rule: any, value: any, callback: any) => {
  if (_rule.allowEmpty && value === '') return callback();
  const a = /^(\d{4})-(\d{2})-(\d{2})$/;
  if (value !== undefined && !a.test(value)) {
    return callback(new Error(_rule.message || '日期格式有误'));
  }
  return callback();
};

const checkDateTimeValidator = (_rule: any, value: any, callback: any) => {
  // console.log(11111, _rule, value)
  if (_rule.allowEmpty && value === '') return callback();
  const a = /^(\d+)-(\d{1,2})-(\d{1,2}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/;
  if (value !== undefined && !a.test(value)) {
    return callback(new Error(_rule.message || '日期时间格式有误'));
  }
  return callback();
};

const checkNumberValidator = (_rule: any, value: any, callback: any) => {
  if (value === '' && _rule.allowEmpty) return callback();
  if (typeof value !== 'number') {
    return callback(new Error(_rule.message || _rule.field + ' 字段不是数值'));
  }
  return callback();
};

const checkRegValidator = (_rule: any, value: any, callback: any) => {
  if (!_rule.pattern.test(value)) {
    return callback(new Error(_rule.message || _rule.field + '正则验证失败'));
  }
  return callback();
};
